/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.seppiko.chart.utils;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Dimension;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.datamatrix.encoder.DefaultPlacement;
import com.google.zxing.datamatrix.encoder.ErrorCorrection;
import com.google.zxing.datamatrix.encoder.HighLevelEncoder;
import com.google.zxing.datamatrix.encoder.SymbolInfo;
import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
import com.google.zxing.pdf417.PDF417Writer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.EnumMap;
import org.seppiko.chart.models.BarcodeEntity;
import org.seppiko.chart.utils.logging.LoggingManager;

/**
 * @author Leonard Woo
 */
public class ZxingUtil {

  private static final LoggingManager logger = LoggingManager.getInstance().getLogger(ZxingUtil.class.getName());

  public static QrCode getQrCode() {
    return new QrCode();
  }

  public static class QrCode {
    public ByteMatrix encode(BarcodeEntity p) throws WriterException {
      EnumMap<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
      if (!StandardCharsets.ISO_8859_1.equals(Charset.forName(p.getEncoding()))) {
        // Only set if not QR code default
        hints.put(EncodeHintType.CHARACTER_SET, p.getEncoding());
      }
      return Encoder.encode(p.getData(), parseErrorCorrectLevel(p.getErrorCorrectionLevel()), hints).getMatrix();
    }

    public static ErrorCorrectionLevel parseErrorCorrectLevel(String ecl) {
      if ("L".equals(ecl) || "l".equals(ecl)) {
        return ErrorCorrectionLevel.L;
      } else if ("Q".equals(ecl) || "q".equals(ecl)) {
        return ErrorCorrectionLevel.Q;
      } else if ("M".equals(ecl) || "m".equals(ecl)) {
        return ErrorCorrectionLevel.M;
      } else if ("H".equals(ecl) || "h".equals(ecl)) {
        return ErrorCorrectionLevel.H;
      } else {
        logger.info("Invalid error correct level : " + ecl);
        throw new IllegalArgumentException("Invalid error correct level : " + ecl);
      }
    }
  }

  public static Pdf417 getPdf417() {
    return new Pdf417();
  }

  public static class Pdf417 {
    public BitMatrix encode(BarcodeEntity p) throws WriterException {
      EnumMap<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
      hints.put(EncodeHintType.CHARACTER_SET, p.getEncoding());
      hints.put(EncodeHintType.ERROR_CORRECTION, p.getErrorCorrectionLevel());
      hints.put(EncodeHintType.MARGIN, p.getMargin());

      return new PDF417Writer().encode(p.getData(), BarcodeFormat.PDF_417, p.getWidth(), p.getHeight(), hints);
    }
  }

  public static Datamatrix getDatamatrix() {
    return new Datamatrix();
  }

  public static class Datamatrix {
    public ByteMatrix encode(String contents) {
      SymbolShapeHint shape = SymbolShapeHint.FORCE_SQUARE;
      Dimension minSize = null;
      Dimension maxSize = null;

      // 1. step: Data encodation
      String encoded = HighLevelEncoder.encodeHighLevel(contents, shape, minSize, maxSize);

      SymbolInfo symbolInfo = SymbolInfo.lookup(encoded.length(), shape, minSize, maxSize, true);

      // 2. step: ECC generation
      String codewords = ErrorCorrection.encodeECC200(encoded, symbolInfo);

      // 3. step: Module placement in Matrix
      DefaultPlacement placement = new DefaultPlacement(codewords, symbolInfo.getSymbolDataWidth(),
          symbolInfo.getSymbolDataHeight());
      placement.place();

      // 4. step: low-level encoding
      return encodeLowLevel(placement, symbolInfo);
    }

    private ByteMatrix encodeLowLevel(DefaultPlacement placement, SymbolInfo symbolInfo) {
      int symbolWidth = symbolInfo.getSymbolDataWidth();
      int symbolHeight = symbolInfo.getSymbolDataHeight();

      ByteMatrix matrix = new ByteMatrix(symbolInfo.getSymbolWidth(), symbolInfo.getSymbolHeight());

      int matrixY = 0;

      for (int y = 0; y < symbolHeight; y++) {
        // Fill the top edge with alternate 0 / 1
        int matrixX;
        if ((y % symbolInfo.matrixHeight) == 0) {
          matrixX = 0;
          for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) {
            matrix.set(matrixX, matrixY, (x % 2) == 0);
            matrixX++;
          }
          matrixY++;
        }
        matrixX = 0;
        for (int x = 0; x < symbolWidth; x++) {
          // Fill the right edge with full 1
          if ((x % symbolInfo.matrixWidth) == 0) {
            matrix.set(matrixX, matrixY, true);
            matrixX++;
          }
          matrix.set(matrixX, matrixY, placement.getBit(x, y));
          matrixX++;
          // Fill the right edge with alternate 0 / 1
          if ((x % symbolInfo.matrixWidth) == symbolInfo.matrixWidth - 1) {
            matrix.set(matrixX, matrixY, (y % 2) == 0);
            matrixX++;
          }
        }
        matrixY++;
        // Fill the bottom edge with full 1
        if ((y % symbolInfo.matrixHeight) == symbolInfo.matrixHeight - 1) {
          matrixX = 0;
          for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) {
            matrix.set(matrixX, matrixY, true);
            matrixX++;
          }
          matrixY++;
        }
      }

      return matrix;
    }
  }

//  public static final char CR = '\r';
  public static final char LF = '\n';
//  public static final String CRLF = "" + CR + LF;

  public static String convertByteArrays(byte[][] bas) {
    return convertByteArrays(bas, "" + LF);
  }

  public static String convertByteArrays(byte[][] bas, String nl) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < bas.length; i++) {
      for (int j = 0; j < bas[0].length; j++) {
        sb.append(bas[i][j]);
      }
      sb.append(nl);
    }
    return sb.toString();
  }
}
